﻿using LIT.Logger.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace LIT.Logger.ServerCheckerBL.Checkers
{
    public class Certificates
    {
        IEventSender m_eventSender;
        IConfiguration m_config;

        public Certificates(IEventSender _eventSender, IConfiguration _config)
        {
            if (_eventSender == null)
                throw new ArgumentNullException("_eventSender");
            if (_config == null)
                throw new ArgumentNullException("_config");
            this.m_eventSender = _eventSender;
            this.m_config = _config;
        }

        private string getFullStoreName(StoreLocation _location, string _storeName)
        {
            return _location.ToString() + "/" + _storeName;
        }

        private List<X509Certificate2> CheckCertStore(StoreLocation _location, string _storeName)
        {
            System.Security.Cryptography.X509Certificates.X509Store xs = new X509Store(_storeName, _location);
            xs.Open(OpenFlags.OpenExistingOnly);
            List<X509Certificate2> certList = new List<X509Certificate2>();
            int total = 0; int invalid = 0;
            foreach (X509Certificate2 cert in xs.Certificates)
            {
                string exp = cert.GetExpirationDateString();
                DateTime expirationDate = DateTime.Parse(exp);
                if (expirationDate < DateTime.Now.AddDays(-Properties.Settings.Default.ErrorDaysBeforeCertificateExpiration))
                {
                    certList.Add(cert);
                    invalid++;
                }
                total++;
            }
            m_eventSender.ReportEvent(new Event("Certificate store check complete", EventType.DEBUG,
                string.Format("{0} ({1}/{2})",
                    getFullStoreName(_location, _storeName),
                    invalid, total)));
            return certList;
        }

        private void CheckAndWriteError(string _storeFullName, List<X509Certificate2> _certificateList)
        {
            StringBuilder certDetails = new StringBuilder();
            foreach (X509Certificate2 certificate in _certificateList)
            {
                if (!m_config.IgnoreCertificateThumbnails.Contains(certificate.Thumbprint))
                {
                    certDetails.AppendLine(string.Format("Issuer {0} Subject {1} Expiration date {2} Thumbprint {3}",
                        certificate.Issuer, certificate.Subject, certificate.GetExpirationDateString(), certificate.Thumbprint));
                }
            }
            if (certDetails.Length > 0)
                m_eventSender.ReportEvent(new Event("Certificate(s) about to expire or has expired", EventType.ERROR) { Details = _storeFullName + "\r\n" + certDetails.ToString() });
        }

        public void CheckCertStores()
        {
            foreach (string storeName in m_config.CheckComputerCertificateStores)
            {
                try
                {
                    List<X509Certificate2> errorCertificates = this.CheckCertStore(StoreLocation.LocalMachine, storeName);
                    CheckAndWriteError(getFullStoreName(StoreLocation.LocalMachine, storeName), errorCertificates);
                }
                catch (Exception ex)
                {
                    this.m_eventSender.ReportException(new ExceptionEvent("Error querying LocalMachine store", EventType.ERROR, ex) { Details = "Store name: " + storeName });
                }
            }
            foreach(string storeName in m_config.CheckUserCertificateStores)
            {
                try
                {
                    List<X509Certificate2> errorCertificates = this.CheckCertStore(StoreLocation.CurrentUser, storeName);
                    CheckAndWriteError(getFullStoreName(StoreLocation.CurrentUser, storeName), errorCertificates);
                }
                catch (Exception ex)
                {
                    this.m_eventSender.ReportException(new ExceptionEvent("Error querying CurrentUser store", EventType.ERROR, ex) { Details = "Store name: " + storeName });
                }
            }
        }

        public static void PrintPossibleStores()
        {
            foreach (StoreLocation storeLocation in (StoreLocation[])
                Enum.GetValues(typeof(StoreLocation)))
            {
                Console.WriteLine("Store location: " + storeLocation);
                foreach (StoreName storeName in (StoreName[])
                    Enum.GetValues(typeof(StoreName)))
                {
                    X509Store store = new X509Store(storeName, storeLocation);
                    try
                    {
                        store.Open(OpenFlags.OpenExistingOnly);
                        Console.WriteLine("({0}) {1}",
                            store.Certificates.Count, store.Name);
                    }
                    catch
                    {
                    }
                }
                Console.WriteLine();
            }
        }
    }
}
